using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Surface;
using Microsoft.Surface.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using SharedUtils;
using System.Xml.Serialization;
using System.IO;


namespace Engine
{
    /// <summary>
    /// Main type for Engine
    /// </summary>
    public class App1 : Microsoft.Xna.Framework.Game
    {
        #region XNA Specific Items
        // Makes stuff appear on screen
        private readonly GraphicsDeviceManager graphics;

        // Manages the importing of assets
        //private ContentManager content;

        // Preps and sends Texture's to graphics device to be rendered
        private SpriteBatch spriteBatch;

        #endregion

        #region PixelSense Specific Items

        private TouchTarget touchTarget;
        private Color backgroundColor = Color.CornflowerBlue;
        private bool applicationLoadCompleteSignalled;

        // PixelSense tables can be used from any side, just makes sure it 
        // loads the project in the right direction when someone starts it
        private UserOrientation currentOrientation = UserOrientation.Bottom;

        // Used to transform view
        private Matrix screenTransform = Matrix.Identity;

        #endregion

        #region Presentation Specific Items

        // Used to deserialize presentations
        private XmlSerializer serializer;

        // Used to read an XML File
        private StreamReader reader;

        // This graph holds all of the presentation's nodes
        private Graph<Node, EdgeData> presentation;
        private Node currentNode;           // Current presentation state
        private Camera2d currentCamera;
        private InputType.InputName currentInput;

        // Interpolation vars
        private Node nextNode;                  // Presentation state to transition to
        private Camera2d interpolationCam;      // Camera used for interpolation
        private double interpolationTime;       // Time span to perform the interpolation
        private int msElapsedSince_Itrp = 0;    // ms passed since timer was started
        private System.Timers.Timer interpolationTimer = new System.Timers.Timer(); //timer set for determining interpolation
        private bool transitionState = false;
        private bool holdTouchPoint = false;


        // Double-Click Vars
        private System.Timers.Timer doubleClickTimer = new System.Timers.Timer(); //timer set for determining dbl-tap/click
        private bool isFirstClick = true;       // bool flag for determining if only 1 click was done
        private bool isDoubleClick = false;     // bool flag for determining if 2 clicks were performed
        private int msElapsedSince_Click = 0;   // ms passed since timer was Started
        private TouchPoint currentTouchPoint;   // area which was pressed currently

        // Timed-Events
        private System.Timers.Timer timedEventTimer = new System.Timers.Timer(); //timer set for determining timed events

        // Sound 
        private Song currSound;
        private SoundEntity.SoundEntityState prevSoundState = SoundEntity.SoundEntityState.STOPPED;
        private SoundEntity.SoundEntityState currSoundState = SoundEntity.SoundEntityState.STOPPED;
        bool soundCollision = false;
        #endregion

        #region Testing
        MouseState old_mouse;

        // How many times tap/hold events occured
        int tapCounter = 0;
        int holdCounter = 0;
        private SharedUtils.Interpolation.InterpolationMode mCameraInterpolationMode;
        #endregion

        /// <summary>
        /// The target receiving all surface input for the application.
        /// </summary>
        protected TouchTarget TouchTarget
        {
            get { return touchTarget; }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>GraphEditor
        public App1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            currentNode = null;
            currentCamera = null;
            presentation = null;
            currentInput = InputType.InputName.None;

            // Interpolation Vars
            nextNode = null;
            interpolationCam = null;
            interpolationTime = 1000; ;
            interpolationTimer.Interval = 5;
            interpolationTimer.Elapsed +=
                new System.Timers.ElapsedEventHandler(interpolationTimer_Elapsed);


            // Double-Click Vars
            doubleClickTimer.Interval = 100;
            doubleClickTimer.Elapsed +=
                new System.Timers.ElapsedEventHandler(doubleClickTimer_Tick);

            // Timed Event Vars
            timedEventTimer.Interval = int.MaxValue;
            timedEventTimer.Elapsed +=
                new System.Timers.ElapsedEventHandler(timedEventTimer_Elapsed);

        }

        /// <summary>
        /// Constructor with path to XML Data.
        /// </summary>
        public App1(String presentationPath)
            : this()
        {
            // Calls method that deserializes XML data
            InitializePresentation(presentationPath);
        }

        public App1(String presentationPath, Color backgroundColor)
            : this(presentationPath)
        {
            this.backgroundColor = backgroundColor;
        }


        /// <summar>
        /// Constructor with graph object
        /// </summar>
        public App1(Graph<Node, EdgeData> g)
            : this()
        {
            Console.WriteLine(Content.RootDirectory);
            presentation = g;
            currentNode = presentation.Nodes[0];
            currentCamera = currentNode.camera;
            setTimedEventTimerInterval();
        }

        public App1(Graph<Node, EdgeData> g, Color backgroundColor)
            : this(g)
        {
            this.backgroundColor = backgroundColor;
        }

        private void setTimedEventTimerInterval()
        {
            List<float> timedEvents = new List<float>();
            foreach (Edge<Node, EdgeData> edge in presentation.GetEdgesFrom(currentNode))
            {
                foreach (InputType input in edge.Data.inputTypes)
                {
                    if (input.inputName.Equals(InputType.InputName.Timed))
                    {
                        timedEvents.Add(input.deltaTime);
                    }
                }
            }


            if (timedEvents.Capacity == 0)
                timedEventTimer.Interval = int.MaxValue;
            else
                timedEventTimer.Interval = findMin(timedEvents) * 1000;
        }

        /// <summary>
        /// Uses XmlSerializer and StreamReader to read in an XML
        /// and deserialize it into the presentation Graph
        /// </summary>
        private void InitializePresentation(String xmlPath)
        {
            serializer = new XmlSerializer(presentation.GetType());

            reader = new StreamReader(xmlPath);

            serializer.Deserialize(reader);
        }


        #region PixleSense Initialization

        /// <summary>
        /// Moves and sizes the window to cover the input surface.
        /// </summary>
        private void SetWindowOnSurface()
        {
            System.Diagnostics.Debug.Assert(Window != null && Window.Handle != IntPtr.Zero,
                "Window initialization must be complete before SetWindowOnSurface is called");
            if (Window == null || Window.Handle == IntPtr.Zero)
                return;

            // Get the window sized right.
            Program.InitializeWindow(Window);
            // Set the graphics device buffers.
            graphics.PreferredBackBufferWidth = Program.WindowSize.Width;
            graphics.PreferredBackBufferHeight = Program.WindowSize.Height;
            graphics.ApplyChanges();
            // Make sure the window is in the right location.
            Program.PositionWindow();
        }

        /// <summary>
        /// Initializes the surface input system. This should be called after any window
        /// initialization is done, and should only be called once.
        /// </summary>
        private void InitializeSurfaceInput()
        {
            System.Diagnostics.Debug.Assert(Window != null && Window.Handle != IntPtr.Zero,
                "Window initialization must be complete before InitializeSurfaceInput is called");
            if (Window == null || Window.Handle == IntPtr.Zero)
                return;
            System.Diagnostics.Debug.Assert(touchTarget == null,
                "Surface input already initialized");
            if (touchTarget != null)
                return;

            // Create a target for surface input.
            touchTarget = new TouchTarget(Window.Handle, EventThreadChoice.OnBackgroundThread);
            touchTarget.EnableInput();
        }

        #endregion

        #region Overridden Game Methods

        /// <summary>
        /// Allows the app to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            IsMouseVisible = true; // easier for debugging not to "lose" mouse
            SetWindowOnSurface();
            InitializeSurfaceInput();

            // Set the application's orientation based on the orientation at launch
            currentOrientation = ApplicationServices.InitialOrientation;

            // Subscribe to surface window availability events
            ApplicationServices.WindowInteractive += OnWindowInteractive;
            ApplicationServices.WindowNoninteractive += OnWindowNoninteractive;
            ApplicationServices.WindowUnavailable += OnWindowUnavailable;

            // Setup the UI to transform if the UI is rotated.
            // Create a rotation matrix to orient the screen so it is viewed correctly
            // when the user orientation is 180 degress different.
            Matrix inverted = Matrix.CreateRotationZ(MathHelper.ToRadians(180)) *
                       Matrix.CreateTranslation(graphics.GraphicsDevice.Viewport.Width,
                                                 graphics.GraphicsDevice.Viewport.Height,
                                                 0);

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }

            // Fire event based on what kind of input was detected
            this.touchTarget.TouchTapGesture += OnTapEvent;
            this.touchTarget.TouchHoldGesture += OnHoldEvent;
            this.touchTarget.TouchMove += OnSwipeEvent;

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per app and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            foreach (Node node in presentation.Nodes)
            {
                foreach (Entity entity in node.entities)
                {
                    if (entity is PictureEntity)
                    {
                        PictureEntity pEntity = (PictureEntity)entity;
                        pEntity.picture = Content.Load<Texture2D>(pEntity.picturePath);
                    }
                    else if (entity is SoundEntity)
                    {
                        SoundEntity sEntity = (SoundEntity)entity;
                        sEntity.sound = Content.Load<Song>(sEntity.soundPath);
                        sEntity.stop = Content.Load<Texture2D>(sEntity.stopPath);
                        sEntity.play = Content.Load<Texture2D>(sEntity.playPath);
                        sEntity.pause = Content.Load<Texture2D>(sEntity.pausePath);
                    }
                    else
                    {
                        throw new Exception();
                    }


                }
            }
        }

        /// <summary>
        /// UnloadContent will be called once per app and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the app to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (ApplicationServices.WindowAvailability != WindowAvailability.Unavailable)
            {
                if (ApplicationServices.WindowAvailability == WindowAvailability.Interactive)
                {
                    if (transitionState)
                    {

                    }
                    else
                    {
                        // If tthe current window is available and the user 
                        // is allowed to interact handle the input logic
                        HandleInput(gameTime);
                    }
                }

                // TODO: Add your update logic here
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// Allows for input logic.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected void HandleInput(GameTime gameTime)
        {
            // Mouse inputs
            Camera2d cam = currentNode.camera;
            Vector2 mp = getMouseVpos();
            // TODO: Process touches, dunno if this stuff is needed yet
            // use the following code to get the state of all current touch points.
            // ReadOnlyTouchPointCollection touches = touchTarget.GetState();
            if (Mouse.GetState().LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
            {
                cam.Move(new Vector2((mp.X - old_mouse.X) * -0.5f, (mp.Y - old_mouse.Y) * -0.5f));
            }

            if (Mouse.GetState().ScrollWheelValue != old_mouse.ScrollWheelValue)
            {
                if (cam.Zoom > 1)
                {
                    cam.Zoom += (Mouse.GetState().ScrollWheelValue - old_mouse.ScrollWheelValue) / 120.0f / 10.0f;
                }
                else
                {
                    cam.Zoom *= 1 + (Mouse.GetState().ScrollWheelValue - old_mouse.ScrollWheelValue) / 120.0f / 20.0f;
                }
            }
            if (Mouse.GetState().RightButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed)
            {
                cam.Rotation += ((mp.X - old_mouse.X) / 500.0f);
                cam.Rotation += ((mp.Y - old_mouse.Y) / 500.0f);
            }

            old_mouse = Mouse.GetState();

            // Tap Inputs
            if (currentInput != InputType.InputName.None)
            {
                timedEventTimer.Stop();

                // Holds entity touch point collided with 
                // Null if it wasn't on an Entity
                Entity collided = checkCollided(currentTouchPoint);

                Console.WriteLine(holdCounter++ + " " + currentInput + " Event Occured on: " + (collided == null ? "Nothing" : collided.name) + ", currentNode: " + currentNode.name);

                // Used to find next node
                bool found = false;

                // If the Hold Event collided with an Entity
                // and the hold was done by a finger
                // && holdEvent.TouchPoint.IsFingerRecognized
                // Finger code taken out because it's not necessary
                if (collided != null || currentInput == InputType.InputName.Timed)
                {
                    // Check all the outgoing edges of currentNode for <Entity, HoldEvent> pair
                    // If there's a match start transition to new Node
                    foreach (Edge<Node, EdgeData> edge in presentation.GetEdgesFrom(currentNode))
                    {

                        // If transition node has not yet been found
                        if (found == false)
                        {
                            // and edge being checked contains collided Entity
                            if (edge.Data.ent.Equals(collided) || currentInput == InputType.InputName.Timed)
                            {
                                // Check the list of valid inputs needed to travel along that edge
                                // if input can be a "Hold" or "None" input, transition to new node
                                foreach (InputType input in edge.Data.inputTypes)
                                {
                                    if (input.inputName.Equals(currentInput))
                                    {
                                        found = true;

                                        // Transition currentNode to new node
                                        TransitionNode(edge);

                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            currentInput = InputType.InputName.None;
            if (!holdTouchPoint)
            {
                currentTouchPoint = null;
                soundCollision = false;
            }

            timedEventTimer.Start();
        }

        // Get the modified Mouse Position w.r.t. the world (standard scale)
        public Vector2 getMouseVpos()
        {
            Vector2 mp = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
            return (mp + currentNode.camera._pos);
        }

        // Convert a point in the camera view into a point in the world view i.e.  invert camera transformations
        public Vector2 cameraToWorldCoord(Vector2 vc)
        {
            return (vc / currentNode.camera.Zoom + currentNode.camera._pos);
        }

        public Rectangle cameraToWorld(Rectangle rc)
        {
            Vector2 xy = cameraToWorldCoord(new Vector2(rc.X, rc.Y));
            //Vector2 hw = cameraToWorldScale(new Vector2(rc.Height, rc.Width));
            //return new Rectangle((int)xy.X, (int)xy.Y, (int)hw.X, (int)hw.Y);
            return new Rectangle((int)xy.X, (int)xy.Y, rc.Width, rc.Height);
        }

        /// <summary>
        /// This is called when the app should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (!applicationLoadCompleteSignalled)
            {
                // Dismiss the loading screen now that we are starting to draw
                ApplicationServices.SignalApplicationLoadComplete();
                applicationLoadCompleteSignalled = true;
            }

            // Won't need this line when we have pretty pictures
            GraphicsDevice.Clear(backgroundColor);

            // Prep spriteBatch using current Camera transformation
            //TODO: Rotate the UI based on the value of screenTransform here if desired
            spriteBatch.Begin(SpriteSortMode.BackToFront,
                        BlendState.AlphaBlend,
                        null,
                        null,
                        null,
                        null,
                        currentCamera.get_transformation(graphics.GraphicsDevice, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height));

            // In the current node of the graph, cycle through each entity and 
            // draw each one if visibility is set to "true"
            foreach (Entity entity in currentNode.entities)
            {
                if (entity.isVisible)
                {
                    Texture2D currTexture;
                    Vector2 currSize;
                    // If Sound Entity draw proper Texture
                    if (entity is SoundEntity)
                    {
                        SoundEntity sEntity = (SoundEntity)entity;
                        currSize = sEntity.size;
                        switch (sEntity.state)
                        {
                            case SoundEntity.SoundEntityState.STOPPED:
                                // When Stopped show the stop button
                                currTexture = sEntity.stop;
                                break;
                            case SoundEntity.SoundEntityState.PLAYING:
                                // When playing show the pause button
                                currTexture = sEntity.pause;
                                break;
                            case SoundEntity.SoundEntityState.PAUSED:
                                // When paused show the play button
                                currTexture = sEntity.play;
                                break;
                            default:
                                throw new Exception();

                        }
                    }
                    else if (entity is PictureEntity)
                    {
                        PictureEntity pEntity = (PictureEntity)entity;
                        currTexture = pEntity.picture;
                        currSize = pEntity.pictureSize;
                    }
                    else 
                    {
                        throw new Exception();
                    }
                    spriteBatch.Draw(currTexture,  // texture
                    entity.position,   // position
                    null,               // sourceRectangle
                    Color.White,        // color
                    0.0f,               // rotation
                    Vector2.Zero,       // origin
                    currSize,   // scale
                    SpriteEffects.None, // effects
                    0.0f);              // layerDepth
                }
            }

            // Close spriteBatch
            spriteBatch.End();

            base.Draw(gameTime);
        }

        #endregion

        #region Application Event Handlers

        /// <summary>
        /// This is called when the user can interact with the application's window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowInteractive(object sender, EventArgs e)
        {
            //TODO: Enable audio, animations here

            //TODO: Optionally enable raw image here
        }

        /// <summary>
        /// This is called when the user can see but not interact with the application's window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowNoninteractive(object sender, EventArgs e)
        {
            //TODO: Disable audio here if it is enabled

            //TODO: Optionally enable animations here
        }

        /// <summary>
        /// This is called when the application's window is not visible or interactive.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnWindowUnavailable(object sender, EventArgs e)
        {
            //TODO: Disable audio, animations here

            //TODO: Disable raw image if it's enabled
        }

        private void timedEventTimer_Elapsed(object sender, EventArgs e)
        {
            currentTouchPoint = null;
            currentInput = InputType.InputName.Timed;
        }

        private void interpolationTimer_Elapsed(object sender, EventArgs e)
        {
            msElapsedSince_Itrp += (int)interpolationTimer.Interval;
            Camera2d from = currentNode.camera;
            Camera2d to = nextNode.camera;
            float x = Interpolation.interpolate(from.Pos.X, to.Pos.X, (float)msElapsedSince_Itrp / (float)interpolationTime, mCameraInterpolationMode);
            float y = Interpolation.interpolate(from.Pos.Y, to.Pos.Y, (float)msElapsedSince_Itrp / (float)interpolationTime, mCameraInterpolationMode);
            float z = Interpolation.interpolate(from.Zoom, to.Zoom, (float)msElapsedSince_Itrp / (float)interpolationTime, mCameraInterpolationMode);
            interpolationCam.Pos = new Vector2(x, y);
            interpolationCam.Zoom = z;

            if (msElapsedSince_Itrp >= (float)interpolationTime)
            {
                interpolationTimer.Stop();
                msElapsedSince_Itrp = 0;
                finishTransition();
            }
        }

        private void finishTransition()
        {
            currentNode = nextNode;
            nextNode = null;
            currentCamera = currentNode.camera;
            transitionState = false;
            setTimedEventTimerInterval();
        }


        private float findMin(List<float> l)
        {
            float min = float.MaxValue;
            foreach (float f in l)
            {
                min = Math.Min(min, f);
            }
            return min;
        }

        private void doubleClickTimer_Tick(object sender, EventArgs e)
        {
            msElapsedSince_Click += (int)doubleClickTimer.Interval;
            // The timer has reached the double click time limit.
            if (msElapsedSince_Click >= SystemInformation.DoubleClickTime)
            {
                doubleClickTimer.Stop();

                // Holds entity touch point collided with 
                // Null if it wasn't on an Entity
                Entity collided = checkCollided(currentTouchPoint);
                //Find unix timestamp (seconds since 01/01/1970)
                long ticks = currentTouchPoint.FrameTimestamp - DateTime.Parse("01/01/1970 00:00:00").Ticks;
                ticks /= 10000; //Convert windows ticks to milliseconds
                string timestamp = ticks.ToString();
                Console.WriteLine("d:" + ticks / 86400000 + "h:" + (ticks % 86400000) / 3600000 + "m:" + ((ticks % 86400000) % 3600000) / 60000 + "s:" + (((ticks % 86400000) % 3600000) % 60000) / 1000 + "ms:" + (((ticks % 86400000) % 3600000) % 60000) % 1000);

                if (isDoubleClick)
                {
                    Console.WriteLine("Perform double click action");
                    Console.WriteLine(tapCounter++ + " Double Tap Event Occured on: " + (collided == null ? "Nothing" : collided.name) + ", currentNode: " + currentNode.name + ", at: " + currentTouchPoint.FrameTimestamp + ", at" + timestamp);
                    currentInput = InputType.InputName.DoubleTap;
                }
                else
                {
                    Console.WriteLine(tapCounter++ + " Tap Event Occured on: " + (collided == null ? "Nothing" : collided.name) + ", currentNode: " + currentNode.name + ", at: " + currentTouchPoint.FrameTimestamp + ", at" + timestamp);
                    currentInput = InputType.InputName.Tap;

                }

                holdTouchPoint = false;
                // Allow the MouseDown event handler to process clicks again.
                isFirstClick = true;
                isDoubleClick = false;
                msElapsedSince_Click = 0;
            }
        }

        /// <summary>
        /// This is called when there is a Tap Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTapEvent(object sender, EventArgs e)
        {
            timedEventTimer.Stop();
            // Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs tapEvent = e as TouchEventArgs;
            holdTouchPoint = true;
            currentTouchPoint = tapEvent.TouchPoint;

            // This is the first mouse click.
            if (isFirstClick)
            {
                isFirstClick = false;

                // Start the double click timer.
                doubleClickTimer.Start();
            }

            // This is the second mouse click.
            else
            {
                // Verify that the mouse click is within the double click
                // rectangle and is within the system-defined double 
                // click period.
                if (msElapsedSince_Click < SystemInformation.DoubleClickTime)
                {
                    isDoubleClick = true;
                }
            }
        }

        /// <summary>
        /// This is called when there is a Hold Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnHoldEvent(object sender, EventArgs e)
        {
            timedEventTimer.Stop();
            // Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs holdEvent = e as TouchEventArgs;
            currentTouchPoint = holdEvent.TouchPoint;
            currentInput = InputType.InputName.Hold;
        }

        /// <summary>
        /// This is called when there is a Swipe Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnSwipeEvent(object sender, EventArgs e)
        {
            // Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs swipeEvent = e as TouchEventArgs;
            currentTouchPoint = swipeEvent.TouchPoint;
        }

        private void OnSwipeUpEvent(object sender, EventArgs e)
        {

            //Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs swipeEvent = e as TouchEventArgs;
            currentTouchPoint = swipeEvent.TouchPoint;
            currentInput = InputType.InputName.SwipeUp;
        }

        private void OnSwipeDownEvent(object sender, EventArgs e)
        {
            //Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs swipeEvent = e as TouchEventArgs;
            currentTouchPoint = swipeEvent.TouchPoint;
            currentInput = InputType.InputName.SwipeDown;
        }

        private void OnSwipeLeftEvent(object sender, EventArgs e)
        {
            //Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs swipeEvent = e as TouchEventArgs;
            currentTouchPoint = swipeEvent.TouchPoint;
            currentInput = InputType.InputName.SwipeLeft;
        }

        private void OnSwipeRightEvent(object sender, EventArgs e)
        {
            //Cast sender as a TouchEventArgs object so 
            // we can access TouchPoint data
            TouchEventArgs swipeEvent = e as TouchEventArgs;
            currentTouchPoint = swipeEvent.TouchPoint;
            currentInput = InputType.InputName.SwipeRight;
        }

        #endregion

        #region TouchPoint Collision

        /// <summary>
        /// Used to check if a touch point was on an Entity
        /// TODO: Adjust for CAMERA
        /// </summary>
        /// <param name="touch"></param>
        protected Entity checkCollided(TouchPoint touch)
        {
            if (touch == null)
            {
                return null;
            }
            // Turn touch point into Rectangle
            Rectangle touchArea = new Rectangle((int)(touch.X),
                        (int)(touch.Y),
                        (int)(touch.Bounds.Width),
                        (int)(touch.Bounds.Height));

            touchArea = cameraToWorld(touchArea);
            // Scan through each entity and see if touch point collided
            // TODO check if Entity is visible or not before doing collision
            foreach (Entity entity in currentNode.entities)
            {
                soundCollision = false;
                Rectangle entityArea = new Rectangle();
                Texture2D currTexture;
                Vector2 currSize;
                if (entity is PictureEntity)
                {
                    PictureEntity cEntity = (PictureEntity)entity;
                    currTexture = cEntity.picture;
                    currSize = cEntity.pictureSize;
                }
                else if (entity is SoundEntity)
                {
                    SoundEntity cEntity = (SoundEntity)entity;
                    currSize = cEntity.size;
                    soundCollision = true;
                    switch (cEntity.state)
                    {
                        case SoundEntity.SoundEntityState.STOPPED:
                            // If stopped need to listen for trigger on play texture
                            currTexture = cEntity.play;
                            break;
                        case SoundEntity.SoundEntityState.PLAYING:
                            // if is playing need to listen for trigger on pause or stop texture
                            currTexture = cEntity.pause;
                            break;
                        case SoundEntity.SoundEntityState.PAUSED:
                            // if pause need to listen for trigger on play texture
                            currTexture = cEntity.play;
                            break;
                        default:
                            throw new Exception();
                    }
                }
                else
                {
                    throw new Exception();
                }
                entityArea = new Rectangle((int)(entity.position.X),
                            (int)(entity.position.Y),
                            (int)(currTexture.Width * currSize.X),
                            (int)(currTexture.Height * currSize.Y));

                if (touchArea.Intersects(entityArea))
                {
                    return entity;
                }
            }

            return null;
        }

        #endregion

        /// <summary>
        /// Method to play sound
        /// </summary>
        /// <param name="edge"></param>
        private void PlayMusic(Song song)
        {
            // Due to the way the MediaPlayer plays music,
            // we have to catch the exception. Music will play when the game is not tethered
            try
            {
                // Play the sound clip
                if (prevSoundState == SoundEntity.SoundEntityState.STOPPED)
                    MediaPlayer.Play(song);
                else if (prevSoundState == SoundEntity.SoundEntityState.PAUSED)
                    MediaPlayer.Resume();
                else
                    throw new Exception();

                // Loop the currently playing song
                MediaPlayer.IsRepeating = true;
            }
            catch { }
        }

        /// <summary>
        /// Method called when Transitioning to new node
        /// </summary>
        /// <param name="edge"></param>
        protected void TransitionNode(Edge<Node, EdgeData> edge)
        {
            nextNode = edge.End;
            transitionState = true;

            // Interpolation Control
            mCameraInterpolationMode = edge.Data.cameraType;
            if (mCameraInterpolationMode == Interpolation.InterpolationMode.NONE)
            {
                finishTransition();
            }
            else
            {
                interpolationCam = currentCamera.copy();
                currentCamera = interpolationCam;
                interpolationTimer.Start();
            }
            // Sound Control
            if (soundCollision)
            {
                SoundEntity e = (SoundEntity)checkCollided(currentTouchPoint);
                prevSoundState = currSoundState;
                currSoundState = e.state;
                currSound = e.sound;
                if (currSoundState == SoundEntity.SoundEntityState.PLAYING &&
                    currSoundState != prevSoundState)
                    PlayMusic(currSound);
                else if (currSoundState == SoundEntity.SoundEntityState.PAUSED)
                    MediaPlayer.Pause();
                else if (currSoundState == SoundEntity.SoundEntityState.STOPPED)
                    MediaPlayer.Stop();
                soundCollision = false;
            }
        }

        #region IDisposable

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Release managed resources.
                IDisposable graphicsDispose = graphics as IDisposable;
                if (graphicsDispose != null)
                {
                    graphicsDispose.Dispose();
                }
                if (touchTarget != null)
                {
                    touchTarget.Dispose();
                    touchTarget = null;
                }
            }

            // Release unmanaged Resources.

            // Set large objects to null to facilitate garbage collection.

            base.Dispose(disposing);
        }

        #endregion
    }
}
